/**
 * Copyright (c) Daniel Centore
 *
 * ex: set filetype=java expandtab tabstop=4 shiftwidth=4 :
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation.
 */
package ronp.anim.undo;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;

/**
 * Utility for making deep copies (vs. clone()'s shallow copies) of
 * objects. Objects are first serialized and then deserialized. Error
 * checking is fairly minimal in this implementation. If an object is
 * encountered that cannot be serialized (or that references an object
 * that cannot be serialized) an error is printed to System.err and
 * null is returned. Depending on your specific application, it might
 * make more sense to have copy(...) re-throw the exception.
 * 
 * Credits: http://javatechniques.com/blog/faster-deep-copies-of-java-objects/
 */
public class DeepCopy<E>
{

	/**
	 * Returns a copy of the object, or null if the object cannot
	 * be serialized.
	 */
	@SuppressWarnings("unchecked")
	public E copy(E orig)
	{
		E obj = null;
		try
		{
			// Write the object out to a byte array
			FastByteArrayOutputStream fbos = new FastByteArrayOutputStream();
			ObjectOutputStream out = new ObjectOutputStream(fbos);
			out.writeObject(orig);
			out.flush();
			out.close();

			// Retrieve an input stream from the byte array and read
			// a copy of the object back in.
			ObjectInputStream in = new ObjectInputStream(fbos.getInputStream());
			obj = (E) in.readObject();
		} catch (IOException e)
		{
			e.printStackTrace();
		} catch (ClassNotFoundException cnfe)
		{
			cnfe.printStackTrace();
		}
		return obj;
	}

}

/**
 * ByteArrayOutputStream implementation that doesn't synchronize methods
 * and doesn't copy the data on toByteArray().
 */
class FastByteArrayOutputStream extends OutputStream
{
	/**
	 * Buffer and size
	 */
	protected byte[] buf = null;
	protected int size = 0;

	/**
	 * Constructs a stream with buffer capacity size 5K
	 */
	public FastByteArrayOutputStream()
	{
		this(5 * 1024);
	}

	/**
	 * Constructs a stream with the given initial size
	 */
	public FastByteArrayOutputStream(int initSize)
	{
		this.size = 0;
		this.buf = new byte[initSize];
	}

	/**
	 * Ensures that we have a large enough buffer for the given size.
	 */
	private void verifyBufferSize(int sz)
	{
		if (sz > buf.length)
		{
			byte[] old = buf;
			buf = new byte[Math.max(sz, 2 * buf.length)];
			System.arraycopy(old, 0, buf, 0, old.length);
			old = null;
		}
	}

	public int getSize()
	{
		return size;
	}

	/**
	 * Returns the byte array containing the written data. Note that this
	 * array will almost always be larger than the amount of data actually
	 * written.
	 */
	public byte[] getByteArray()
	{
		return buf;
	}

	@Override
	public final void write(byte b[])
	{
		verifyBufferSize(size + b.length);
		System.arraycopy(b, 0, buf, size, b.length);
		size += b.length;
	}

	@Override
	public final void write(byte b[], int off, int len)
	{
		verifyBufferSize(size + len);
		System.arraycopy(b, off, buf, size, len);
		size += len;
	}

	@Override
	public final void write(int b)
	{
		verifyBufferSize(size + 1);
		buf[size++] = (byte) b;
	}

	public void reset()
	{
		size = 0;
	}

	/**
	 * Returns a ByteArrayInputStream for reading back the written data
	 */
	public InputStream getInputStream()
	{
		return new FastByteArrayInputStream(buf, size);
	}

}

/**
 * ByteArrayInputStream implementation that does not synchronize methods.
 */
class FastByteArrayInputStream extends InputStream
{
	/**
	 * Our byte buffer
	 */
	protected byte[] buf = null;

	/**
	 * Number of bytes that we can read from the buffer
	 */
	protected int count = 0;

	/**
	 * Number of bytes that have been read from the buffer
	 */
	protected int pos = 0;

	public FastByteArrayInputStream(byte[] buf, int count)
	{
		this.buf = buf;
		this.count = count;
	}

	@Override
	public final int available()
	{
		return count - pos;
	}

	@Override
	public final int read()
	{
		return (pos < count) ? (buf[pos++] & 0xff) : -1;
	}

	@Override
	public final int read(byte[] b, int off, int len)
	{
		if (pos >= count)
			return -1;

		if ((pos + len) > count)
			len = (count - pos);

		System.arraycopy(buf, pos, b, off, len);
		pos += len;
		return len;
	}

	@Override
	public final long skip(long n)
	{
		if ((pos + n) > count)
			n = count - pos;
		if (n < 0)
			return 0;
		pos += n;
		return n;
	}

}